﻿using System;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using FirstFloor.Documents.Controls;
using FirstFloor.Documents.Annotations;
using FirstFloor.Documents.Annotations.Storage;
using System.ComponentModel;

namespace SampleBrowser.Common.Annotations
{
    /// <summary>
    /// Represents the behavior that implements an Ink editor capable of adding and removing ink strokes.
    /// </summary>
    public class InkEditor
        : Behavior<FixedPageViewer>
    {
        private Ink ink;        // the ink annotation being edited
        private InkPresenter presenter; // the InkPresenter used for handling mouse events.
        private Stroke currentStroke;
        private StylusPointCollection erasePoints;
        private InkEditorSettings settings;

        /// <summary>
        /// Initializes a new instance of the <see cref="InkEditor"/> class.
        /// </summary>
        public InkEditor()
        {
            this.Settings = new InkEditorSettings();
        }

        /// <summary>
        /// Gets or sets the settings.
        /// </summary>
        /// <value>The setting.</value>
        public InkEditorSettings Settings
        {
            get { return this.settings; }
            set
            {
                if (this.settings != value) {
                    if (this.settings != null) {
                        this.settings.PropertyChanged -= OnSettingsPropertyChanged;
                    }
                    this.settings = value;
                    this.settings.PropertyChanged += OnSettingsPropertyChanged;
                }
            }
        }

        private void OnSettingsPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            UpdateState();
        }

        private void UpdateState()
        {
            if (this.presenter != null) {
                this.presenter.Cursor = this.settings.IsEnabled ? Cursors.Stylus : null;
            }

            this.currentStroke = null;
            this.erasePoints = null;
        }

        /// <summary>
        /// Gets or sets the annotation store.
        /// </summary>
        /// <value>The store.</value>
        public AnnotationStore Store { get; set; }

        /// <summary>
        /// Called after the behavior is attached to an AssociatedObject.
        /// </summary>
        /// <remarks>Override this to hook up functionality to the AssociatedObject.</remarks>
        protected override void OnAttached()
        {
            base.OnAttached();

            this.AssociatedObject.FixedPageChanged += OnFixedPageChanged;
        }

        /// <summary>
        /// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
        /// </summary>
        /// <remarks>Override this to unhook functionality from the AssociatedObject.</remarks>
        protected override void OnDetaching()
        {
            base.OnDetaching();

            this.AssociatedObject.FixedPageChanged -= OnFixedPageChanged;
        }

        private void OnFixedPageChanged(object sender, EventArgs e)
        {
            if (this.AssociatedObject.FixedPage == null) {
                // page is unloaded, clear event handlers
                if (this.presenter != null) {
                    this.presenter.MouseEnter -= OnMouseEnter;
                    this.presenter.MouseLeftButtonDown -= OnMouseLeftButtonDown;
                    this.presenter.MouseLeftButtonUp -= OnMouseLeftButtonUp;
                    this.presenter.MouseMove -= OnMouseMove;
                    this.presenter.LostMouseCapture -= OnLostMouseCapture;
                }
            }
            else {
                // get the Ink annotation associated with this page from the store
                this.ink = (from annotation in this.Store.OfType<Ink>()
                            where annotation.PageNumber == this.AssociatedObject.FixedPage.PageNumber
                            select annotation).FirstOrDefault();

                if (this.ink == null) {
                    // no annotation found, create a new one for this page
                    this.ink = new Ink() {
                        PageNumber = this.AssociatedObject.FixedPage.PageNumber
                    };
                    // and add to the store
                    this.Store.Add(this.ink);
                }

                // create the Ink presenter
                this.presenter = new InkPresenter() {
                    Width = this.AssociatedObject.FixedPage.Width,
                    Height = this.AssociatedObject.FixedPage.Height,
                    Background = new SolidColorBrush(Colors.Transparent),
                    Cursor = Cursors.Stylus
                };

                UpdateState();

                // add all strokes of the annotation
                foreach (var stroke in this.ink.Strokes) {
                    this.presenter.Strokes.Add(stroke);
                }

                this.presenter.MouseEnter += OnMouseEnter;
                this.presenter.MouseLeftButtonDown += OnMouseLeftButtonDown;
                this.presenter.MouseLeftButtonUp += OnMouseLeftButtonUp;
                this.presenter.MouseMove += OnMouseMove;
                this.presenter.LostMouseCapture += OnLostMouseCapture;

                this.AssociatedObject.FixedPage.Children.Add(this.presenter);
            }
        }

        private void OnMouseEnter(object sender, MouseEventArgs e)
        {
            if (!this.Settings.IsEnabled) {
                return;
            }

            // set the appropiate cursor
            this.presenter.Cursor = this.Settings.StrokeColor == null ? Cursors.Eraser : Cursors.Stylus;
        }

        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!this.Settings.IsEnabled) {
                return;
            }

            e.Handled = true;

            if (this.Settings.StrokeColor == null) {
                // using the stroke erase pattern (see also Silverlight documentation)
                this.erasePoints = e.StylusDevice.GetStylusPoints(this.presenter);
            }
            else {
                this.presenter.CaptureMouse();
                var points = new StylusPointCollection();
                points.Add(e.StylusDevice.GetStylusPoints(this.presenter));

                this.currentStroke = new Stroke(points) {
                    DrawingAttributes = new DrawingAttributes() {
                        Color = this.Settings.StrokeColor.Value,
                        Width = this.Settings.StrokeWidth,
                        Height = this.Settings.StrokeHeight
                    }
                };
                this.presenter.Strokes.Add(this.currentStroke);
            }
        }

        private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            this.erasePoints = null;
        }

        private void OnLostMouseCapture(object sender, MouseEventArgs e)
        {
            if (this.currentStroke != null) {
                // update the annotation
                this.ink.Strokes.Add(this.currentStroke);
            }

            this.currentStroke = null;
        }

        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (!this.Settings.IsEnabled) {
                return;
            }

            if (this.Settings.StrokeColor == null) {
                if (this.erasePoints != null) {
                    this.erasePoints.Add(e.StylusDevice.GetStylusPoints(this.presenter));
                    // compare the collected stylus points with the stroke collection of the ink presenter.
                    StrokeCollection hitStrokes = this.presenter.Strokes.HitTest(this.erasePoints);
                    // delete the strokes that intersect with the collected stylus points.
                    if (hitStrokes.Count > 0) {
                        foreach (Stroke hitStroke in hitStrokes) {
                            this.presenter.Strokes.Remove(hitStroke);
                            // and update the annotation
                            this.ink.Strokes.Remove(hitStroke);
                        }
                    }
                }
            }
            else if (this.currentStroke != null) {
                this.currentStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(this.presenter));
            }
        }
    }
}
